// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <inttypes.h>
#include <lib/sync/completion.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/hw/usb/hub.h>
#include <zircon/listnode.h>

#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/usb.h>
#include <ddk/protocol/usb/bus.h>
#include <ddk/protocol/usb/hub.h>
#include <usb/usb-request.h>
#include <usb/usb.h>

// usb_port_status_t.wPortStatus
typedef uint16_t port_status_t;

typedef struct usb_hub {
  // the device we are publishing
  zx_device_t* zxdev;

  // Underlying USB device
  zx_device_t* usb_device;
  usb_protocol_t usb;

  usb_bus_protocol_t bus;

  usb_speed_t hub_speed;
  int num_ports;
  // delay after port power in microseconds
  zx_time_t power_on_delay;

  usb_request_t* status_request;
  sync_completion_t completion;

  thrd_t thread;
  atomic_bool thread_done;

  // port status values for our ports
  // length is num_ports
  port_status_t* port_status;

  size_t parent_req_size;

  // bit field indicating which ports have devices attached
  uint8_t attached_ports[128 / 8];
} usb_hub_t;

bool usb_hub_is_port_attached(usb_hub_t* hub, int port) {
  return (hub->attached_ports[port / 8] & (1 << (port % 8))) != 0;
}

void usb_hub_set_port_attached(usb_hub_t* hub, int port, bool enabled) {
  if (enabled) {
    hub->attached_ports[port / 8] |= (1 << (port % 8));
  } else {
    hub->attached_ports[port / 8] &= ~(1 << (port % 8));
  }
}

static zx_status_t usb_hub_get_port_status(usb_hub_t* hub, int port, port_status_t* out_status) {
  usb_port_status_t status;

  size_t out_length;
  zx_status_t result = usb_get_status(&hub->usb, USB_RECIP_PORT, port, &status, sizeof(status),
                                      ZX_TIME_INFINITE, &out_length);
  if (result != ZX_OK) {
    return result;
  }
  if (out_length != sizeof(status)) {
    return ZX_ERR_BAD_STATE;
  }

  zxlogf(TRACE, "usb_hub_get_port_status port %d ", port);

  uint16_t port_change = status.wPortChange;
  if (port_change & USB_C_PORT_CONNECTION) {
    zxlogf(TRACE, "USB_C_PORT_CONNECTION ");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONNECTION, port,
                      ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_ENABLE) {
    zxlogf(TRACE, "USB_C_PORT_ENABLE ");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_ENABLE, port, ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_SUSPEND) {
    zxlogf(TRACE, "USB_C_PORT_SUSPEND ");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_SUSPEND, port,
                      ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_OVER_CURRENT) {
    zxlogf(TRACE, "USB_C_PORT_OVER_CURRENT ");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_OVER_CURRENT, port,
                      ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_RESET) {
    zxlogf(TRACE, "USB_C_PORT_RESET");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_RESET, port, ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_BH_PORT_RESET) {
    zxlogf(TRACE, "USB_C_BH_PORT_RESET");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_BH_PORT_RESET, port,
                      ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_LINK_STATE) {
    zxlogf(TRACE, "USB_C_PORT_LINK_STATE");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_LINK_STATE, port,
                      ZX_TIME_INFINITE);
  }
  if (port_change & USB_C_PORT_CONFIG_ERROR) {
    zxlogf(TRACE, "USB_C_PORT_CONFIG_ERROR");
    usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONFIG_ERROR, port,
                      ZX_TIME_INFINITE);
  }
  zxlogf(TRACE, "\n");

  *out_status = status.wPortStatus;
  return ZX_OK;
}

static zx_status_t usb_hub_wait_for_port(usb_hub_t* hub, int port, port_status_t* out_status,
                                         port_status_t status_bits, port_status_t status_mask,
                                         zx_time_t stable_time) {
  const zx_time_t timeout = ZX_SEC(2);       // 2 second total timeout
  const zx_time_t poll_delay = ZX_MSEC(25);  // poll every 25 milliseconds
  zx_time_t total = 0;
  zx_time_t stable = 0;

  while (total < timeout) {
    zx_nanosleep(zx_deadline_after(poll_delay));
    total += poll_delay;

    zx_status_t result = usb_hub_get_port_status(hub, port, out_status);
    if (result != ZX_OK) {
      return result;
    }
    hub->port_status[port] = *out_status;

    if ((*out_status & status_mask) == status_bits) {
      stable += poll_delay;
      if (stable >= stable_time) {
        return ZX_OK;
      }
    } else {
      stable = 0;
    }
  }

  return ZX_ERR_TIMED_OUT;
}

static void usb_hub_interrupt_complete(void* ctx, usb_request_t* req) {
  zxlogf(TRACE, "usb_hub_interrupt_complete got %d %" PRIu64 "\n", req->response.status,
         req->response.actual);
  usb_hub_t* hub = (usb_hub_t*)ctx;
  sync_completion_signal(&hub->completion);
}

static void usb_hub_power_on_port(usb_hub_t* hub, int port) {
  usb_set_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_PORT_POWER, port, ZX_TIME_INFINITE);
  usleep(hub->power_on_delay);
}

static void usb_hub_port_enabled(usb_hub_t* hub, int port) {
  port_status_t status;

  zxlogf(TRACE, "port %d usb_hub_port_enabled\n", port);

  // USB 2.0 spec section 9.1.2 recommends 100ms delay before enumerating
  // wait for USB_PORT_ENABLE == 1 and USB_PORT_RESET == 0
  if (usb_hub_wait_for_port(hub, port, &status, USB_PORT_ENABLE, USB_PORT_ENABLE | USB_PORT_RESET,
                            ZX_MSEC(100)) != ZX_OK) {
    zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_RESET failed for USB hub, port %d\n", port);
    return;
  }

  usb_speed_t speed;
  if (hub->hub_speed == USB_SPEED_SUPER) {
    speed = USB_SPEED_SUPER;
  } else if (status & USB_PORT_LOW_SPEED) {
    speed = USB_SPEED_LOW;
  } else if (status & USB_PORT_HIGH_SPEED) {
    speed = USB_SPEED_HIGH;
  } else {
    speed = USB_SPEED_FULL;
  }

  zxlogf(TRACE, "call hub_device_added for port %d\n", port);
  usb_bus_device_added(&hub->bus, hub->usb_device, port, speed);
  usb_hub_set_port_attached(hub, port, true);
}

static void usb_hub_port_connected(usb_hub_t* hub, int port) {
  port_status_t status;

  zxlogf(TRACE, "port %d usb_hub_port_connected\n", port);

  // USB 2.0 spec section 7.1.7.3 recommends 100ms between connect and reset
  if (usb_hub_wait_for_port(hub, port, &status, USB_PORT_CONNECTION, USB_PORT_CONNECTION,
                            ZX_MSEC(100)) != ZX_OK) {
    zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_CONNECTION failed for USB hub, port %d\n", port);
    return;
  }

  usb_set_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port, ZX_TIME_INFINITE);
  usb_hub_port_enabled(hub, port);
}

static zx_status_t usb_hub_port_reset(void* ctx, uint32_t port) {
  port_status_t status;
  usb_hub_t* hub = ctx;

  usb_set_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port, ZX_TIME_INFINITE);
  status = usb_hub_wait_for_port(hub, port, &status, USB_PORT_ENABLE,
                                 USB_PORT_ENABLE | USB_PORT_RESET, ZX_MSEC(100));
  if (status != ZX_OK) {
    zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_RESET failed for USB hub, port %d\n", port);
  }
  return status;
}

static usb_hub_interface_protocol_ops_t _hub_interface = {
    .reset_port = usb_hub_port_reset,
};

static void usb_hub_port_disconnected(usb_hub_t* hub, int port) {
  zxlogf(TRACE, "port %d usb_hub_port_disconnected\n", port);
  usb_bus_device_removed(&hub->bus, hub->usb_device, port);
  usb_hub_set_port_attached(hub, port, false);
}

static void usb_hub_handle_port_status(usb_hub_t* hub, int port, port_status_t status) {
  port_status_t old_status = hub->port_status[port];

  zxlogf(TRACE, "usb_hub_handle_port_status port: %d status: %04X old_status: %04X\n", port, status,
         old_status);

  hub->port_status[port] = status;

  if ((status & USB_PORT_CONNECTION) && !(status & USB_PORT_ENABLE)) {
    // Handle race condition where device is quickly disconnected and reconnected.
    // This happens when Android devices switch USB configurations.
    // In this case, any change to the connect state should trigger a disconnect
    // before handling a connect event.
    if (usb_hub_is_port_attached(hub, port)) {
      usb_hub_port_disconnected(hub, port);
      old_status &= ~USB_PORT_CONNECTION;
    }
  }
  bool port_enable_cleared = !(status & USB_PORT_ENABLE) && (old_status & USB_PORT_ENABLE);

  if ((status & USB_PORT_CONNECTION) && !(old_status & USB_PORT_CONNECTION)) {
    usb_hub_port_connected(hub, port);
    // Check for both USB_PORT_CONNECTION and USB_PORT_ENABLE below.
    // It seems to be a quirk of newer x86 hardware that USB_PORT_ENABLE might be set
    // but USB_PORT_CONNECTION is not.
  } else if (!(status & USB_PORT_CONNECTION) &&
             (old_status & USB_PORT_CONNECTION || port_enable_cleared)) {
    usb_hub_port_disconnected(hub, port);
  } else if ((status & USB_PORT_ENABLE) && !(old_status & USB_PORT_ENABLE)) {
    usb_hub_port_enabled(hub, port);
  }
}

static void usb_hub_unbind(void* ctx) {
  usb_hub_t* hub = ctx;
  for (int port = 1; port <= hub->num_ports; port++) {
    if (usb_hub_is_port_attached(hub, port)) {
      usb_hub_port_disconnected(hub, port);
    }
  }

  atomic_store(&hub->thread_done, true);
  sync_completion_signal(&hub->completion);
  thrd_join(hub->thread, NULL);

  device_remove(hub->zxdev);
}

static zx_status_t usb_hub_free(usb_hub_t* hub) {
  usb_request_release(hub->status_request);
  free(hub->port_status);
  free(hub);
  return ZX_OK;
}

static void usb_hub_release(void* ctx) {
  usb_hub_t* hub = ctx;
  usb_hub_free(hub);
}

static zx_protocol_device_t usb_hub_device_proto = {
    .version = DEVICE_OPS_VERSION,
    .unbind = usb_hub_unbind,
    .release = usb_hub_release,
};

static int usb_hub_thread(void* arg) {
  usb_hub_t* hub = (usb_hub_t*)arg;
  usb_request_t* req = hub->status_request;

  usb_hub_descriptor_t hub_desc;
  size_t out_length;
  int desc_type = (hub->hub_speed == USB_SPEED_SUPER ? USB_HUB_DESC_TYPE_SS : USB_HUB_DESC_TYPE);
  zx_status_t result =
      usb_get_descriptor(&hub->usb, USB_TYPE_CLASS | USB_RECIP_DEVICE, desc_type, 0, &hub_desc,
                         sizeof(hub_desc), ZX_TIME_INFINITE, &out_length);
  if (result < 0) {
    zxlogf(ERROR, "get hub descriptor failed: %d\n", result);
    goto fail;
  }
  // The length of the descriptor varies depending on whether it is USB 2.0 or 3.0,
  // and how many ports it has.
  size_t min_length = 7;
  size_t max_length = sizeof(hub_desc);
  if (out_length < min_length || out_length > max_length) {
    zxlogf(ERROR, "get hub descriptor got length %lu, want length between %lu and %lu\n",
           out_length, min_length, max_length);
    result = ZX_ERR_BAD_STATE;
    goto fail;
  }

  // Determine whether the hub supports a single or multiple transaction-translators.  For
  // low or full-speed devices, this information is encoded in the bDeviceProtocol field of a
  // DEVICE_QUALIFIER descriptor.  For high-speed devices, this information is encoded in the
  // bDeviceProtocol field of a DEVICE descriptor.  See: USB 2.0 spec. 11.23.
  bool multi_tt = false;
  if (hub->hub_speed == USB_SPEED_LOW || hub->hub_speed == USB_SPEED_FULL) {
    usb_device_qualifier_descriptor_t qual_desc;
    result = usb_get_descriptor(&hub->usb, USB_TYPE_STANDARD, USB_DT_DEVICE_QUALIFIER, 0,
                                &qual_desc, sizeof(qual_desc), ZX_TIME_INFINITE, &out_length);
    if (result < 0) {
      zxlogf(ERROR, "get device_qualifier descriptor failed: %d\n", result);
      goto fail;
    } else if (out_length != sizeof(qual_desc)) {
      zxlogf(ERROR, "get device_qualifier descriptor returned %ld bytes, want %ld\n", out_length,
             sizeof(qual_desc));
      result = ZX_ERR_BAD_STATE;
      goto fail;
    }
    multi_tt = qual_desc.bDeviceProtocol == 2;
  } else if (hub->hub_speed == USB_SPEED_HIGH) {
    usb_device_descriptor_t dev_desc;
    result = usb_get_descriptor(&hub->usb, USB_TYPE_STANDARD, USB_DT_DEVICE, 0, &dev_desc,
                                sizeof(dev_desc), ZX_TIME_INFINITE, &out_length);
    if (result < 0) {
      zxlogf(ERROR, "get device descriptor failed: %d\n", result);
      goto fail;
    } else if (out_length != sizeof(dev_desc)) {
      zxlogf(ERROR, "get device descriptor returned %ld bytes, want %ld\n", out_length,
             sizeof(dev_desc));
      result = ZX_ERR_BAD_STATE;
      goto fail;
    }
    multi_tt = dev_desc.bDeviceProtocol == 2;
  } else {  // super-speed
    // USB 3.x devices do not support the concept of transaction-translators.
    multi_tt = false;
  }

  result = usb_bus_configure_hub(&hub->bus, hub->usb_device, hub->hub_speed, &hub_desc, multi_tt);
  if (result < 0) {
    zxlogf(ERROR, "configure_hub failed: %d\n", result);
    goto fail;
  }

  int num_ports = hub_desc.bNbrPorts;
  hub->num_ports = num_ports;
  hub->port_status = calloc(num_ports + 1, sizeof(port_status_t));
  if (!hub->port_status) {
    result = ZX_ERR_NO_MEMORY;
    goto fail;
  }

  // power on delay in microseconds
  hub->power_on_delay = hub_desc.bPowerOn2PwrGood * 2 * 1000;
  if (hub->power_on_delay < 100 * 1000) {
    // USB 2.0 spec section 9.1.2 recommends at least 100ms delay after power on
    hub->power_on_delay = 100 * 1000;
  }

  for (int i = 1; i <= num_ports; i++) {
    usb_hub_power_on_port(hub, i);
  }

  device_make_visible(hub->zxdev);

  // bit field for port status bits
  uint8_t status_buf[128 / 8];
  memset(status_buf, 0, sizeof(status_buf));

  usb_request_complete_t complete = {
      .callback = usb_hub_interrupt_complete,
      .ctx = hub,
  };

  // This loop handles events from our interrupt endpoint
  while (1) {
    sync_completion_reset(&hub->completion);
    usb_request_queue(&hub->usb, req, &complete);
    sync_completion_wait(&hub->completion, ZX_TIME_INFINITE);
    if (req->response.status != ZX_OK || atomic_load(&hub->thread_done)) {
      break;
    }

    usb_request_copy_from(req, status_buf, req->response.actual, 0);
    uint8_t* bitmap = status_buf;
    uint8_t* bitmap_end = bitmap + req->response.actual;

    // bit zero is hub status
    if (bitmap[0] & 1) {
      // what to do here?
      zxlogf(ERROR, "usb_hub_interrupt_complete hub status changed\n");
    }

    int port = 1;
    int bit = 1;
    while (bitmap < bitmap_end && port <= num_ports) {
      if (*bitmap & (1 << bit)) {
        port_status_t status;
        zx_status_t result = usb_hub_get_port_status(hub, port, &status);
        if (result == ZX_OK) {
          usb_hub_handle_port_status(hub, port, status);
        }
      }
      port++;
      if (++bit == 8) {
        bitmap++;
        bit = 0;
      }
    }
  }

  return ZX_OK;

fail:
  device_remove(hub->zxdev);
  return result;
}

static zx_status_t usb_hub_bind(void* ctx, zx_device_t* device) {
  usb_protocol_t usb;
  zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
  if (status != ZX_OK) {
    return status;
  }

  usb_bus_protocol_t bus;
  status = device_get_protocol(device, ZX_PROTOCOL_USB_BUS, &bus);
  if (status != ZX_OK) {
    zxlogf(ERROR, "usb_hub_bind could not find bus device\n");
    return status;
  }

  usb_desc_iter_t iter;
  status = usb_desc_iter_init(&usb, &iter);
  if (status < 0) {
    return status;
  }

  usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
  if (!intf || intf->bNumEndpoints != 1) {
    usb_desc_iter_release(&iter);
    return ZX_ERR_NOT_SUPPORTED;
  }

  usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
  if (!endp || usb_ep_type(endp) != USB_ENDPOINT_INTERRUPT) {
    usb_desc_iter_release(&iter);
    return ZX_ERR_NOT_SUPPORTED;
  }

  usb_ss_ep_comp_descriptor_t* ss_comp_desc = NULL;
  usb_descriptor_header_t* desc = usb_desc_iter_next(&iter);
  if (desc && desc->bDescriptorType == USB_DT_SS_EP_COMPANION) {
    ss_comp_desc = (usb_ss_ep_comp_descriptor_t*)desc;
  }

  uint8_t ep_addr = endp->bEndpointAddress;
  uint16_t max_packet_size = usb_ep_max_packet(endp);

  usb_hub_t* hub = calloc(1, sizeof(usb_hub_t));
  if (!hub) {
    zxlogf(ERROR, "Not enough memory for usb_hub_t.\n");
    usb_desc_iter_release(&iter);
    return ZX_ERR_NO_MEMORY;
  }
  atomic_init(&hub->thread_done, false);

  hub->usb_device = device;
  hub->hub_speed = usb_get_speed(&usb);
  memcpy(&hub->usb, &usb, sizeof(usb_protocol_t));
  memcpy(&hub->bus, &bus, sizeof(usb_bus_protocol_t));

  hub->parent_req_size = usb_get_request_size(&usb);

  usb_request_t* req;
  status = usb_request_alloc(&req, max_packet_size, ep_addr, hub->parent_req_size);
  if (status != ZX_OK) {
    usb_desc_iter_release(&iter);
    usb_hub_free(hub);
    return status;
  }
  hub->status_request = req;

  status = usb_enable_endpoint(&usb, endp, ss_comp_desc, true);
  if (status != ZX_OK) {
    zxlogf(ERROR, "%s: usb_enable_endpoint failed %d\n", __FUNCTION__, status);
    usb_desc_iter_release(&iter);
    usb_hub_free(hub);
    return status;
  }

  usb_desc_iter_release(&iter);

  device_add_args_t args = {
      .version = DEVICE_ADD_ARGS_VERSION,
      .name = "usb-hub",
      .ctx = hub,
      .ops = &usb_hub_device_proto,
      .flags = DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE,
  };

  status = device_add(hub->usb_device, &args, &hub->zxdev);
  if (status != ZX_OK) {
    usb_hub_free(hub);
    return status;
  }

  usb_bus_set_hub_interface(&bus, hub->usb_device, hub, &_hub_interface);

  int ret = thrd_create_with_name(&hub->thread, usb_hub_thread, hub, "usb_hub_thread");
  if (ret != thrd_success) {
    device_remove(hub->zxdev);
    return ZX_ERR_NO_MEMORY;
  }

  return ZX_OK;
}

static zx_driver_ops_t usb_hub_driver_ops = {
    .version = DRIVER_OPS_VERSION,
    .bind = usb_hub_bind,
};

ZIRCON_DRIVER_BEGIN(usb_hub, usb_hub_driver_ops, "zircon", "0.1", 2)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_DEVICE),
    BI_MATCH_IF(EQ, BIND_USB_CLASS, USB_CLASS_HUB), ZIRCON_DRIVER_END(usb_hub)
